﻿using UnityEngine;
using System.Collections;
using System.Text;
using System;
using System.Collections.Generic;
using ZXthex.Char;

class CSSStyleSheetBuilder
{
    enum TokenizationState
    {
        Outside,
        TypeSelector,
        ClassSelector,
        IDSelector,
        Or,
        Sub,
        BeforeAttributeName,
        AttributeName,
        AfterAttributeName,
        BeforeAttributeValue,
        AttributeValue,
        AfterAttributeValue,
        BeforePropertyName,
        AfterSelector,
        PropertyName,
        AfterPropertyName,
        BeforePropertyValue,
        AfterPropertyValue,
        PropertyValue,


    }

    enum BuildTokenType
    {
        Or,
        Sub,
        TypeSelector,
        ClassSelector,
        IDSelector,
        AttributeName,
        AttributeValue,
        PropertyName,
        PropertyValue,
        PropertyOver
    }

    class BuildToken
    {
        public BuildTokenType type { get; private set; }
        public BuildToken(BuildTokenType type)
        {
            this.type = type;
            sBuilder = new StringBuilder(8);
        }

        private StringBuilder sBuilder;
        public string text { get { return sBuilder.ToString(); } }
        public void Append(char c)
        {
            sBuilder.Append(c);
        }

    }

    class Property
    {

    }

    class CSSRule
    {
        string _id;
        string _type;
        string _class;


        public bool MatchCondition(ReadOnlyHtmlNode node)
        {
            bool flag = true;
            if (!string.IsNullOrEmpty(_id) && node.Id != _id)
                flag = false;

            if (!string.IsNullOrEmpty(_type) && node.Name != _type)
                flag = false;

            if (!string.IsNullOrEmpty(_class) && node.GetAttribute("class").Value != _class)
                flag = false;

            return flag;
        }
    }

    private static CSSStyleSheetBuilder _default = new CSSStyleSheetBuilder();

    public static CSSStyleSheetBuilder Default { get { return _default; } }

    public void LoadCSS(NextChars reader, out I_CSSRule[] rules)
    {
        CheckTrueEncoding(reader);

        PreprocessInputStream(reader);

        var tokens = DoTokenization(reader);

        DoTreeConstruction(tokens, out rules);
    }

    private void CheckTrueEncoding(NextChars reader)
    {
    }

    private void PreprocessInputStream(NextChars reader)
    {
    }

    private List<BuildToken> DoTokenization(NextChars reader)
    {
        reader.ResetPosition();

        List<BuildToken> tokens = new List<BuildToken>();

        BuildToken token = null;
        TokenizationState state = TokenizationState.Outside;

        char c = '\0';

        bool EOF = false;

        bool should_reconsume = false;

        bool comment = false;

        while (true)
        {
            if (!should_reconsume)
            {
                if (reader.CanGoNext)
                {
                    c = reader.NextChar();
                }
                else
                {
                    EOF = true;
                }
            }
            else
            {
                should_reconsume = false;
            }

            if (!comment && c == '/' && reader.PeekFewChars(1) == "*")
            {
                comment = true;
                reader.NextChar();
                continue;
            }

            if (comment && c == '*' && reader.PeekFewChars(1) == "/")
            {
                comment = false;
                reader.NextChar();
                continue;
            }
            else if (comment)
            {
                continue;
            }


            switch (state)
            {
                case TokenizationState.Outside:
                    if (c.IsIgnoreChar())
                    {
                        //ignore
                    }
                    else if (c == '#')
                    {
                        state = TokenizationState.IDSelector;
                        token = new BuildToken(BuildTokenType.IDSelector);
                    }
                    else if (c == '.')
                    {
                        state = TokenizationState.ClassSelector;
                        token = new BuildToken(BuildTokenType.ClassSelector);
                    }
                    else if (c.IsIdentifier())
                    {
                        state = TokenizationState.TypeSelector;
                        token = new BuildToken(BuildTokenType.TypeSelector);
                        should_reconsume = true;
                    }
                    else if (c == '[')
                    {
                        state = TokenizationState.BeforeAttributeName;

                    }

                    break;
                case TokenizationState.TypeSelector:
                    if (c.IsIdentifier())
                    {
                        token.Append(c);
                    }
                    else if (c.IsIgnoreChar())
                    {
                        tokens.Add(token);
                        state = TokenizationState.AfterSelector;
                    }
                    else if (c == '#')
                    {
                        tokens.Add(token);
                        state = TokenizationState.IDSelector;
                        token = new BuildToken(BuildTokenType.IDSelector);
                    }
                    else if (c == '.')
                    {
                        tokens.Add(token);
                        state = TokenizationState.ClassSelector;
                        token = new BuildToken(BuildTokenType.ClassSelector);

                    }
                    else if (c == '[')
                    {
                        tokens.Add(token);
                        state = TokenizationState.BeforeAttributeName;

                    }
                    else if (c == '{')
                    {
                        tokens.Add(token);
                        state = TokenizationState.BeforePropertyName;
                    }
                    else if (c == ',')
                    {
                        tokens.Add(token);
                        token = new BuildToken(BuildTokenType.Or);
                        tokens.Add(token);
                        state = TokenizationState.Or;
                    }
                    else if (c == '>')
                    {
                        tokens.Add(token);
                        token = new BuildToken(BuildTokenType.Sub);
                        tokens.Add(token);
                        state = TokenizationState.Sub;
                    }


                    break;
                case TokenizationState.ClassSelector:
                    if (c.IsIdentifier())
                    {
                        token.Append(c);
                    }
                    else if (c.IsIgnoreChar())
                    {
                        tokens.Add(token);
                        state = TokenizationState.AfterSelector;
                    }
                    else if (c == '#')
                    {
                        tokens.Add(token);
                        state = TokenizationState.IDSelector;
                        token = new BuildToken(BuildTokenType.IDSelector);
                    }
                    else if (c == '[')
                    {
                        tokens.Add(token);
                        state = TokenizationState.BeforeAttributeName;

                    }
                    else if (c == '{')
                    {
                        tokens.Add(token);
                        state = TokenizationState.BeforePropertyName;
                    }
                    else if (c == ',')
                    {
                        tokens.Add(token);
                        token = new BuildToken(BuildTokenType.Or);
                        tokens.Add(token);
                        state = TokenizationState.Or;
                    }
                    else if (c == '>')
                    {
                        tokens.Add(token);
                        token = new BuildToken(BuildTokenType.Sub);
                        tokens.Add(token);
                        state = TokenizationState.Sub;
                    }
                    break;
                case TokenizationState.IDSelector:
                    if (c.IsIdentifier())
                    {
                        token.Append(c);
                    }
                    else if (c.IsIgnoreChar())
                    {
                        tokens.Add(token);
                        state = TokenizationState.AfterSelector;
                    }
                    else if (c == '.')
                    {
                        tokens.Add(token);
                        state = TokenizationState.ClassSelector;
                        token = new BuildToken(BuildTokenType.ClassSelector);

                    }
                    else if (c == '[')
                    {
                        tokens.Add(token);
                        state = TokenizationState.BeforeAttributeName;

                    }
                    else if (c == '{')
                    {
                        tokens.Add(token);
                        state = TokenizationState.BeforePropertyName;
                    }
                    else if (c == ',')
                    {
                        tokens.Add(token);
                        token = new BuildToken(BuildTokenType.Or);
                        tokens.Add(token);
                        state = TokenizationState.Or;
                    }
                    else if (c == '>')
                    {
                        tokens.Add(token);
                        token = new BuildToken(BuildTokenType.Sub);
                        tokens.Add(token);
                        state = TokenizationState.Sub;
                    }
                    break;
                case TokenizationState.BeforeAttributeName:
                    if (c.IsIgnoreChar())
                    {
                        //ignore
                    }
                    else if (c.IsIdentifier())
                    {
                        token = new BuildToken(BuildTokenType.AttributeName);
                        should_reconsume = true;
                        state = TokenizationState.AttributeName;
                    }
                    break;
                case TokenizationState.AttributeName:
                    if (c.IsIdentifier())
                    {
                        token.Append(c);
                    }
                    else if (c.IsIgnoreChar())
                    {
                        tokens.Add(token);
                        state = TokenizationState.AfterAttributeName;
                    }
                    else if (c == '=')
                    {
                        tokens.Add(token);
                        state = TokenizationState.BeforeAttributeValue;
                    }
                    break;
                case TokenizationState.AfterAttributeName:
                    if (c.IsIgnoreChar())
                    {
                        //ignore
                    }
                    else if (c == '=')
                    {
                        state = TokenizationState.BeforeAttributeValue;
                    }
                    break;
                case TokenizationState.BeforeAttributeValue:
                    if (c.IsIgnoreChar())
                    {
                        //ignore
                    }
                    else if (c.IsIdentifier())
                    {
                        token = new BuildToken(BuildTokenType.AttributeValue);
                        should_reconsume = true;
                        state = TokenizationState.AttributeValue;
                    }
                    break;
                case TokenizationState.AttributeValue:
                    if (c.IsIdentifier())
                    {
                        token.Append(c);
                    }
                    else if (c.IsIgnoreChar())
                    {
                        tokens.Add(token);
                        state = TokenizationState.AfterAttributeValue;
                    }
                    else if (c == ']')
                    {
                        tokens.Add(token);
                        state = TokenizationState.AfterSelector;
                    }
                    break;
                case TokenizationState.AfterAttributeValue:
                    if (c.IsIgnoreChar())
                    {
                        //ignore
                    }
                    else if (c == ']')
                    {
                        state = TokenizationState.AfterSelector;
                    }
                    break;
                case TokenizationState.Or:
                    if (c.IsIgnoreChar())
                    {
                        //ignore
                    }
                    else if (c == '#')
                    {
                        state = TokenizationState.IDSelector;
                        token = new BuildToken(BuildTokenType.IDSelector);
                    }
                    else if (c == '.')
                    {
                        state = TokenizationState.ClassSelector;
                        token = new BuildToken(BuildTokenType.ClassSelector);
                    }
                    else if (c.IsAlpha())
                    {
                        state = TokenizationState.TypeSelector;
                        token = new BuildToken(BuildTokenType.TypeSelector);
                        should_reconsume = true;
                    }
                    break;
                case TokenizationState.Sub:
                    if (c.IsIgnoreChar())
                    {
                        //ignore
                    }
                    else if (c == '#')
                    {
                        state = TokenizationState.IDSelector;
                        token = new BuildToken(BuildTokenType.IDSelector);
                    }
                    else if (c == '.')
                    {
                        state = TokenizationState.ClassSelector;
                        token = new BuildToken(BuildTokenType.ClassSelector);
                    }
                    else if (c.IsAlpha())
                    {
                        state = TokenizationState.TypeSelector;
                        token = new BuildToken(BuildTokenType.TypeSelector);
                        should_reconsume = true;
                    }
                    break;
                case TokenizationState.AfterSelector:
                    if (c.IsIgnoreChar())
                    {
                        //ignore
                    }
                    else if (c == '#')
                    {
                        state = TokenizationState.IDSelector;
                        token = new BuildToken(BuildTokenType.IDSelector);
                    }
                    else if (c == '.')
                    {
                        state = TokenizationState.ClassSelector;
                        token = new BuildToken(BuildTokenType.ClassSelector);
                    }
                    else if (c.IsAlpha())
                    {
                        state = TokenizationState.TypeSelector;
                        token = new BuildToken(BuildTokenType.TypeSelector);
                        should_reconsume = true;
                    }
                    else if (c == '{')
                    {
                        state = TokenizationState.BeforePropertyName;
                    }
                    else if (c == ',')
                    {
                        token = new BuildToken(BuildTokenType.Or);
                        tokens.Add(token);
                        state = TokenizationState.Or;
                    }
                    else if (c == '>')
                    {
                        token = new BuildToken(BuildTokenType.Sub);
                        tokens.Add(token);
                        state = TokenizationState.Sub;
                    }
                    break;
                case TokenizationState.BeforePropertyName:
                    if (c.IsIgnoreChar())
                    {
                        //ignore
                    }
                    else if (c.IsIdentifier())
                    {
                        should_reconsume = true;
                        state = TokenizationState.PropertyName;
                        token = new BuildToken(BuildTokenType.PropertyName);
                    }
                    else if (c == '}')
                    {
                        tokens.Add(new BuildToken(BuildTokenType.PropertyOver));
                        state = TokenizationState.Outside;
                    }
                    break;
                case TokenizationState.PropertyName:
                    if (c.IsIdentifier())
                    {
                        token.Append(c);
                    }
                    else if (c.IsIgnoreChar())
                    {
                        state = TokenizationState.AfterPropertyName;
                        tokens.Add(token);
                    }
                    else if (c == ':')
                    {
                        state = TokenizationState.BeforePropertyValue;
                        tokens.Add(token);
                    }
                    break;
                case TokenizationState.AfterPropertyName:
                    if (c.IsIgnoreChar())
                    {
                        //ignore
                    }
                    else if (c == ':')
                    {
                        state = TokenizationState.BeforePropertyValue;
                    }
                    break;
                case TokenizationState.BeforePropertyValue:
                    if (c.IsIgnoreChar())
                    {
                        //ignore
                    }
                    else if (c.IsPropertyValue())
                    {
                        should_reconsume = true;
                        state = TokenizationState.PropertyValue;
                        token = new BuildToken(BuildTokenType.PropertyValue);
                    }
                    break;
                case TokenizationState.PropertyValue:
                    if (c.IsIgnoreChar())
                    {
                        tokens.Add(token);
                        state = TokenizationState.AfterPropertyValue;
                    }
                    else if (c == ';')
                    {
                        tokens.Add(token);
                        state = TokenizationState.BeforePropertyName;
                    }
                    else if (c == '}')
                    {
                        tokens.Add(token);
                        tokens.Add(new BuildToken(BuildTokenType.PropertyOver));
                        state = TokenizationState.Outside;
                    }
                    else
                    {
                        token.Append(c);
                    }
                    break;
                case TokenizationState.AfterPropertyValue:
                    if (c.IsIgnoreChar())
                    {
                        //ignore
                    }
                    else if (c == ';')
                    {
                        state = TokenizationState.BeforePropertyName;
                    }
                    else if (c == '}')
                    {
                        tokens.Add(token);
                        tokens.Add(new BuildToken(BuildTokenType.PropertyOver));
                        state = TokenizationState.Outside;
                    }
                    else if (c.IsPropertyValue())
                    {
                        should_reconsume = true;
                        state = TokenizationState.PropertyValue;
                        token = new BuildToken(BuildTokenType.PropertyValue);
                    }
                    break;
                default:
                    throw new System.NotImplementedException(state.ToString());
            }

            if (EOF)
            {
                break;
            }
        }

        return tokens;

    }

    private void DoTreeConstruction(List<BuildToken> tokens, out I_CSSRule[] rules)
    {
        List<I_CSSRule> rule_list = new List<I_CSSRule>(16);

        string id = null, @class = null, type = null;

        string par_id = null, par_class = null, par_type = null;

        List<AttributeSelector> att_list = new List<AttributeSelector>(8);
        string att_name = null;

        List<CSS_Declaration> dec_list = new List<CSS_Declaration>(8);
        string dec_name = null;
        List<string> dec_value = new List<string>(4);

        bool ignoreSelectors = false;

        int goto_token_index_for_or = -1;
        for (int i = 0; i < tokens.Count; i++)
        {

            switch (tokens[i].type)
            {
                case BuildTokenType.TypeSelector:
                case BuildTokenType.ClassSelector:
                case BuildTokenType.IDSelector:
                case BuildTokenType.AttributeName:
                case BuildTokenType.AttributeValue:
                case BuildTokenType.Or:
                    if (ignoreSelectors)
                    {
                        continue;
                    }
                    break;
                case BuildTokenType.PropertyOver:
                    if (ignoreSelectors)
                        ignoreSelectors = false;
                    break;
            }

            //Debug.Log(tokens[i].type+" "+tokens[i].text);

            switch (tokens[i].type)
            {
                case BuildTokenType.TypeSelector:
                    type = tokens[i].text;
                    break;
                case BuildTokenType.ClassSelector:
                    @class = tokens[i].text;
                    break;
                case BuildTokenType.IDSelector:
                    id = tokens[i].text;
                    break;
                case BuildTokenType.AttributeName:
                    att_name = tokens[i].text;
                    break;
                case BuildTokenType.AttributeValue:
                    att_list.Add(new AttributeSelector(att_name, tokens[i].text));
                    att_name = null;
                    break;
                case BuildTokenType.Or:
                    goto_token_index_for_or = i + 1;
                    ignoreSelectors = true;
                    break;
                case BuildTokenType.Sub:
                    par_class = @class;
                    par_type = type;
                    par_id = id;

                    @class = null;
                    type = null;
                    id = null;
                    break;
                case BuildTokenType.PropertyName:
                    if (dec_value.Count > 0)
                    {
                        dec_list.Add(new CSS_Declaration() { name = dec_name, values = dec_value.ToArray() });
                        dec_name = null;
                        dec_value.Clear();
                    }


                    dec_name = tokens[i].text;
                    break;
                case BuildTokenType.PropertyValue:
                    dec_value.Add(tokens[i].text);
                    break;

                case BuildTokenType.PropertyOver:

                    if (dec_value.Count > 0)
                    {
                        dec_list.Add(new CSS_Declaration() { name = dec_name, values = dec_value.ToArray() });
                        dec_name = null;
                        dec_value.Clear();
                    }

                    I_CSSRule rule = new Simple_CSSRule(
                type, id, @class,
                par_type, par_id, par_class,
                att_list.ToArray(), dec_list.ToArray()
                );
                    rule_list.Add(rule);
                    type = null;
                    id = null;
                    @class = null;
                    par_type = null;
                    par_id = null;
                    par_class = null;
                    att_list.Clear();
                    dec_list.Clear();

                    if (goto_token_index_for_or >= 0)
                    {
                        i = goto_token_index_for_or - 1;
                        goto_token_index_for_or = -1;
                    }

                    break;
            }
        }

        rules = rule_list.ToArray();
    }
}
